package com.boot.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.boot.common.anno.AnnoLog;
import com.boot.common.constant.GlobalVar;
import com.boot.system.entity.SysLog;
import com.boot.system.service.LogService;
import com.boot.util.IPUtil;
import com.boot.util.SecurityUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import java.util.Collection;
import java.util.Map;

/**
 * 日志切面
 */
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class LogAspect {
    private final LogService logService;
    private final HttpServletRequest request;

    /**
     * 日志切点
     */
    @Pointcut("@annotation(annoLog)")
    public void logPointcut(AnnoLog annoLog) {
    }

    /**
     * 处理完请求后执行
     */
    @Around(value = "logPointcut(annoLog)", argNames = "joinPoint,annoLog")
    public Object doAround(ProceedingJoinPoint joinPoint, AnnoLog annoLog) throws Throwable {
        // 在方法执行前获取用户，避免在方法执行过程中清除上下文导致获取不到用户
        String username = SecurityUtil.getUsername();
        TimeInterval timer = DateUtil.timer();
        Object result = null;
        Exception exception = null;
        try {
            result = joinPoint.proceed();
            return result;
        } catch (Exception e) {
            exception = e;
            throw e;
        } finally {
            //不记录开发者的操作记录
            if (!"super".equals(username)){
                // 执行时长
                long executionTime = timer.interval();
                // 异步保存日志
                this.saveLogAsync(joinPoint, exception, result, annoLog, executionTime, username);
            }
        }
    }

    /**
     * 异步保存日志
     */
    @Async
    protected void saveLogAsync(final JoinPoint joinPoint, final Exception e, Object jsonResult, AnnoLog annoLog, long executionTime, String username) {
        try {
            this.saveLog(joinPoint, e, jsonResult, annoLog, executionTime, username);
        } catch (Exception ex) {
            log.error("保存日志失败", ex);
        }
    }

    /**
     * 保存日志
     */
    private void saveLog(final JoinPoint joinPoint, final Exception e, Object jsonResult, AnnoLog annoLog, long executionTime, String username) {
        String requestURI = request.getRequestURI();
        // 创建日志记录
        SysLog sysLog = new SysLog();
        sysLog.setUsername(username);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        sysLog.setFunc(signature.getName());
        if (annoLog != null) {
            sysLog.setContent(annoLog.value());
        }
        // 请求参数
        if (annoLog != null && annoLog.params()) {
            this.setRequestParameters(joinPoint, sysLog);
        }
        // 响应结果
        if (annoLog != null && annoLog.result() && jsonResult != null) {
            sysLog.setResponseContent(JSONUtil.toJsonStr(jsonResult));
        }
        //出现异常
        if (e != null) {
            sysLog.setErrorContent(e.toString());
            sysLog.setStatus(0);
        }
        sysLog.setRequestUri(requestURI);
        sysLog.setCreateTime(GlobalVar.systemTime);
        String ip = IPUtil.getIpAddr(request);
        if (StrUtil.isNotBlank(ip)) {
            sysLog.setIp(ip);
        }
        sysLog.setExecTime(executionTime);
        // 获取浏览器和终端系统信息
        String userAgentString = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgentUtil.parse(userAgentString);
        // 系统信息
        sysLog.setOs(userAgent.getOs().getName());
        // 浏览器信息
        sysLog.setBrowser(userAgent.getBrowser().getName());
        sysLog.setBrowserVersion(userAgent.getBrowser().getVersion(userAgentString));
        // 保存日志到数据库
        logService.save(sysLog);
    }

    /**
     * 设置请求参数到日志对象中
     */
    private void setRequestParameters(JoinPoint joinPoint, SysLog syslog) {
        String requestMethod = request.getMethod();
        syslog.setRequestMethod(requestMethod);
        if (HttpMethod.GET.name().equalsIgnoreCase(requestMethod) || 
            HttpMethod.PUT.name().equalsIgnoreCase(requestMethod) || 
            HttpMethod.POST.name().equalsIgnoreCase(requestMethod)) {
            String params = convertArgumentsToString(joinPoint.getArgs());
            syslog.setRequestParams(StrUtil.sub(params, 0, 65535));
        } else {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                Map<?, ?> paramsMap = (Map<?, ?>) attributes.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
                syslog.setRequestParams(StrUtil.sub(paramsMap.toString(), 0, 65535));
            } else {
                syslog.setRequestParams("");
            }
        }
    }

    /**
     * 将参数数组转换为字符串
     */
    private String convertArgumentsToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null) {
            for (Object param : paramsArray) {
                if (!shouldFilterObject(param)) {
                    params.append(JSONUtil.toJsonStr(param)).append(" ");
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象
     */
    private boolean shouldFilterObject(Object obj) {
        if (obj == null) {
            return true;
        }
        Class<?> clazz = obj.getClass();
        if (clazz.isArray()) {
            return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection<?> collection = (Collection<?>) obj;
            return collection.stream().anyMatch(item -> item instanceof MultipartFile);
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map<?, ?> map = (Map<?, ?>) obj;
            return map.values().stream().anyMatch(value -> value instanceof MultipartFile);
        }
        return obj instanceof MultipartFile || obj instanceof HttpServletRequest || obj instanceof HttpServletResponse;
    }
}
